home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue50 / HTML / usXMLDoc.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-08-07  |  10.3 KB  |  414 lines

  1. unit usXMLDoc;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, XmlParser {CueSoft};
  7.  
  8. type
  9.   TusXMLDocument = class;
  10.  
  11.   { TusXMLAttribute - a single tag attribute }
  12.   TusXMLAttribute = class(TPersistent)
  13.   private
  14.     FName: string;
  15.     FValue: string;
  16.   public
  17.     procedure Assign(aSource: TPersistent); override;
  18.     property Name: string read FName write FName;
  19.     property Value: string read FValue write FValue;
  20.   end;
  21.  
  22.   { TusXMLAttributes - a list of all attributes for a tag }
  23.   TusXMLAttributes = class(TPersistent)
  24.   protected
  25.     FList: TList;
  26.     function GetCount: Integer;
  27.     function GetItem(aIndex: Integer): TusXMLAttribute;
  28.   public
  29.     constructor Create;
  30.     destructor Destroy; override;
  31.     procedure Add(aItem: TusXMLAttribute);
  32.     procedure Assign(aSource: TPersistent); override;
  33.     procedure Clear;
  34.     function GetByName(aName: string): TusXMLAttribute;
  35.     function Value(aName: string): string;
  36.     property Count: Integer read GetCount;
  37.     property Items[aIndex: Integer]: TusXMLAttribute read GetItem; default;
  38.   end;
  39.  
  40.   { TusXMLElement - a single element (tag) }
  41.   TusXMLElement = class
  42.   private
  43.     FAttributes: TusXMLAttributes;
  44.     FData: string;
  45.     FLevel: SmallInt;
  46.     FParent: TusXMLElement;
  47.     FSubtags: TusXMLDocument;
  48.     FTagName: string;
  49.   public
  50.     constructor Create;
  51.     destructor Destroy; override;
  52.     property Attributes: TusXMLAttributes read FAttributes;
  53.     property Data: string read FData write FData;
  54.     property Level: SmallInt read FLevel write FLevel;
  55.     property Parent: TusXMLElement read FParent;
  56.     property Subtags: TusXMLDocument read FSubtags;
  57.     property TagName: string read FTagName write FTagName;
  58.   end;
  59.  
  60.   { TusXMLDocument - a contiguous block of XML tags }
  61.   TusXMLDocument = class
  62.   private
  63.   protected
  64.     FList: TList;
  65.     FRoot: TusXMLElement;
  66.     procedure AddElement(aElement: TusXMLElement);
  67.     function CreateNode(aParent: TusXMLElement; aTagName,
  68.       aData: string): TusXMLElement;
  69.     function GetCount: Integer;
  70.     function GetItem(aIndex: Integer): TusXMLElement;
  71.   public
  72.     constructor Create;
  73.     destructor Destroy; override;
  74.     function Add(aSibling: TusXMLElement; aName, aValue: string): TusXMLElement;
  75.     function AddChild(aParent: TusXMLElement; aName, aValue: string): TusXMLElement;
  76.     procedure Clear;
  77.     property Count: Integer read GetCount;
  78.     property Items[aIndex: Integer]: TusXMLElement read GetItem; default;
  79.     property Root: TusXMLElement read FRoot;
  80.   end;
  81.  
  82.   { TusParser - parses raw XML and yields a TusXMLDocument structure }
  83.   TusXMLParser = class
  84.   private
  85.     FDocument: TusXMLDocument;
  86.  
  87.     { The following private declarations are specific to the third-party
  88.       parser being used to implement this class. }
  89.     Parser: TXMLParser;
  90.     NestingLevel: Integer;
  91.     LastElement: TusXMLElement;
  92.     Attributes: TusXMLAttributes;
  93.   protected
  94.     procedure DoOnAttribute(aSender: TObject; aName, aValue: string; aSpecified: Boolean);
  95.     procedure DoOnCDATASection(aSender: TObject; aValue: string);
  96.     procedure DoOnCharData(aSender: TObject; aValue: string);
  97.     procedure DoOnEndElement(aSender: TObject; aValue: string);
  98.     procedure DoOnStartDocument(aSender: TObject);
  99.     procedure DoOnStartElement(aSender: TObject; aValue: string);
  100.   public
  101.     constructor Create;
  102.     destructor Destroy; override;
  103.     procedure LoadXML(aXML: string); virtual;
  104.     property Document: TusXMLDocument read FDocument;
  105.   end;
  106.  
  107. implementation
  108.  
  109. uses
  110.   SysUtils;
  111.  
  112. { TusXMLAttribute }
  113.  
  114. procedure TusXMLAttribute.Assign(aSource: TPersistent);
  115. begin
  116.   if aSource is TusXMLAttribute then
  117.   begin
  118.     FName := TusXMLAttribute(aSource).FName;
  119.     FValue := TusXMLAttribute(aSource).FValue;
  120.   end
  121.   else
  122.     inherited Assign(aSource);
  123. end;
  124.  
  125. { TusXMLAttributes }
  126.  
  127. procedure TusXMLAttributes.Add(aItem: TusXMLAttribute);
  128. begin
  129.   FList.Add(aItem);
  130. end;
  131.  
  132. procedure TusXMLAttributes.Assign(aSource: TPersistent);
  133. var
  134.   I: Integer;
  135. begin
  136.   if aSource is TusXMLAttributes then
  137.   begin
  138.     Clear;
  139.     for I := 0 to TusXMLAttributes(aSource).Count - 1 do
  140.     begin
  141.       Add(TusXMLAttribute.Create);
  142.       Items[Count - 1].Assign(TusXMLAttributes(aSource)[I]);
  143.     end;
  144.   end
  145.   else
  146.     inherited Assign(aSource);
  147. end;
  148.  
  149. procedure TusXMLAttributes.Clear;
  150. var
  151.   I: Integer;
  152. begin
  153.   for I := 0 to Count - 1 do
  154.     Items[I].Free;
  155.   FList.Clear;
  156. end;
  157.  
  158. constructor TusXMLAttributes.Create;
  159. begin
  160.   inherited;
  161.   FList := TList.Create;
  162. end;
  163.  
  164. destructor TusXMLAttributes.Destroy;
  165. begin
  166.   Clear;
  167.   FList.Free;
  168.   inherited;
  169. end;
  170.  
  171. function TusXMLAttributes.GetByName(aName: string): TusXMLAttribute;
  172. var
  173.   I: Integer;
  174. begin
  175.   Result := nil;
  176.   for I := 0 to Count - 1 do
  177.     if CompareText(aName, Items[I].Name) = 0 then
  178.     begin
  179.       Result := Items[I];
  180.       Break;
  181.     end;
  182. end;
  183.  
  184. function TusXMLAttributes.GetCount: Integer;
  185. begin
  186.   Result := FList.Count;
  187. end;
  188.  
  189. function TusXMLAttributes.GetItem(aIndex: Integer): TusXMLAttribute;
  190. begin
  191.   Result := TusXMLAttribute(FList[aIndex]);
  192. end;
  193.  
  194. function TusXMLAttributes.Value(aName: string): string;
  195. var
  196.   Attr: TusXMLAttribute;
  197. begin
  198.   Result := '';
  199.   Attr := GetByName(aName);
  200.   if Assigned(Attr) then
  201.     Result := Attr.Name;
  202. end;
  203.  
  204. { TusXMLElement }
  205.  
  206. constructor TusXMLElement.Create;
  207. begin
  208.   inherited;
  209.   FAttributes := TusXMLAttributes.Create;
  210.   FSubtags := TusXMLDocument.Create;
  211. end;
  212.  
  213. destructor TusXMLElement.Destroy;
  214. begin
  215.   FAttributes.Free;
  216.   FSubtags.Free;
  217.   inherited;
  218. end;
  219.  
  220. { TusXMLDocument }
  221.  
  222. function TusXMLDocument.Add(aSibling: TusXMLElement; aName,
  223.   aValue: string): TusXMLElement;
  224. { Add a new XML element to the list }
  225. begin
  226.   if not Assigned(aSibling) then
  227.   begin
  228.     Result := CreateNode(nil, aName, aValue);
  229.     AddElement(Result);
  230.   end
  231.   else
  232.   begin
  233.     Result := CreateNode(aSibling.Parent, aName, aValue);
  234.     Result.Level := aSibling.Parent.Level + 1;
  235.     aSibling.Parent.Subtags.AddElement(Result);
  236.   end;
  237. end;
  238.  
  239. function TusXMLDocument.AddChild(aParent: TusXMLElement; aName,
  240.   aValue: string): TusXMLElement;
  241. begin
  242.   Assert(Assigned(aParent), 'Parent element not assigned.');
  243.  
  244.   Result := CreateNode(aParent, aName, aValue);
  245.   Result.Level := aParent.Level + 1;
  246.   aParent.Subtags.AddElement(Result);
  247. end;
  248.  
  249. procedure TusXMLDocument.AddElement(aElement: TusXMLElement);
  250. begin
  251.   FList.Add(aElement);
  252.   if not Assigned(FRoot) then
  253.     FRoot := aElement;
  254. end;
  255.  
  256. procedure TusXMLDocument.Clear;
  257. var
  258.   I: Integer;
  259. begin
  260.   for I := 0 to Count - 1 do
  261.     TusXMLElement(FList[I]).Free;
  262.   inherited;
  263. end;
  264.  
  265. constructor TusXMLDocument.Create;
  266. begin
  267.   inherited;
  268.   FList := TList.Create;
  269. end;
  270.  
  271. function TusXMLDocument.CreateNode(aParent: TusXMLElement; aTagName, aData: string): TusXMLElement;
  272. { If aParent is unassigned, then we are added a zero-level node }
  273. begin
  274.   Result := TusXMLElement.Create;
  275.   Result.TagName := AnsiUpperCase(aTagName);
  276.   Result.Data := aData;
  277.   Result.FParent := aParent;
  278. end;
  279.  
  280. destructor TusXMLDocument.Destroy;
  281. begin
  282.   Clear;
  283.   FList.Free;
  284.   inherited;
  285. end;
  286.  
  287. function TusXMLDocument.GetCount: Integer;
  288. begin
  289.   Result := FList.Count;
  290. end;
  291.  
  292. function TusXMLDocument.GetItem(aIndex: Integer): TusXMLElement;
  293. begin
  294.   Result := TusXMLElement(FList[aIndex]);
  295. end;
  296.  
  297. { TusXMLParser }
  298.  
  299. constructor TusXMLParser.Create;
  300. begin
  301.   inherited;
  302.   Parser := TXMLParser.Create(nil);
  303.   with Parser do
  304.   begin
  305.     NormalizeData := True;
  306.     OnStartDocument := DoOnStartDocument;
  307.     OnAttribute := DoOnAttribute;
  308.     OnStartElement := DoOnStartElement;
  309.     OnCDATASection := DoOnCDATASection;
  310.     OnCharData := DoOnCharData;
  311.     OnEndElement := DoOnEndElement;
  312.   end;
  313.   Attributes := TusXMLAttributes.Create;
  314.   FDocument := TusXMLDocument.Create;
  315. end;
  316.  
  317. destructor TusXMLParser.Destroy;
  318. begin
  319.   Attributes.Free;
  320.   FDocument.Free;
  321.   Parser.Free;
  322.   inherited;
  323. end;
  324.  
  325. procedure TusXMLParser.DoOnAttribute(aSender: TObject; aName,
  326.   aValue: string; aSpecified: Boolean);
  327. { OnAttribute is fired BEFORE the OnStartElement for the tag containing
  328.   the attributes.  So we must accumulate the attributes and wait for the
  329.   OnStartElement event. }
  330. var
  331.   A: TusXMLAttribute;
  332. begin
  333.   A := TusXMLAttribute.Create;
  334.   A.Name := ANSILowercase(aName);
  335.   A.Value := aValue;
  336.   Attributes.Add(A);
  337. end;
  338.  
  339. procedure TusXMLParser.DoOnCDATASection(aSender: TObject; aValue: string);
  340. begin
  341.   with LastElement do
  342.   begin
  343.     Data := Data + aValue;
  344.   end;
  345. end;
  346.  
  347. procedure TusXMLParser.DoOnCharData(aSender: TObject; aValue: string);
  348. begin
  349.   with LastElement do
  350.   begin
  351.     Data := Data + aValue;
  352.   end;
  353. end;
  354.  
  355. procedure TusXMLParser.DoOnEndElement(aSender: TObject; aValue: string);
  356. begin
  357.   Dec(NestingLevel);
  358. end;
  359.  
  360. procedure TusXMLParser.DoOnStartDocument(aSender: TObject);
  361. begin
  362.   LastElement := nil;
  363.   NestingLevel := -1;
  364.   Attributes.Clear;
  365. end;
  366.  
  367. procedure TusXMLParser.DoOnStartElement(aSender: TObject; aValue: string);
  368. { On entry: LastElement refers to the last element we created or nil if
  369.   this is the first element.
  370.   We create a new element and LastElement now points to the new element. }
  371. var
  372.   ParentElement: TusXMLElement;
  373.   I: Integer;
  374. begin
  375.   Inc(NestingLevel);
  376.   if not Assigned(LastElement) or (NestingLevel = LastElement.Level) then
  377.     { root element (XML tag), or new sibling of previous element }
  378.     LastElement := FDocument.Add(LastElement, aValue, '')
  379.   else
  380.     if NestingLevel > LastElement.Level then
  381.       { first child of previous element }
  382.       LastElement := FDocument.AddChild(LastElement, aValue, '')
  383.     else
  384.     begin
  385.       { next sibling of previous element's parent }
  386.       ParentElement := LastElement;
  387.       for I := LastElement.Level - 1 downto NestingLevel do
  388.         ParentElement := ParentElement.Parent;
  389.       LastElement := FDocument.Add(ParentElement, aValue, '');
  390.     end;
  391.  
  392.   { Copy attributes }
  393.   LastElement.Attributes.Assign(Attributes);
  394.   Attributes.Clear;
  395. end;
  396.  
  397. procedure TusXMLParser.LoadXML(aXML: string);
  398. var
  399.   ErrorMsg: string;
  400.   I: Integer;
  401. begin
  402.   Document.Clear;
  403.   if not Parser.ParseMemory(PChar(aXML)) then
  404.     with Parser.Errors do
  405.     begin
  406.       ErrorMsg := 'Error parsing UWML:';
  407.       for I := 0 to Count - 1 do
  408.         ErrorMsg := ErrorMsg + #13#10 + Strings[I];
  409.       raise Exception.Create(ErrorMsg);
  410.     end;
  411. end;
  412.  
  413. end.
  414.